package game.resources;

import game.graphics.Animation;
import game.structures.Camera;
import game.structures.Layer;
import game.structures.Player;
import game.structures.Point;

public class TikalPlayer implements Player {
	
	private Animation anim = null;
	private Layer layer;
	
	private Point location;
	private short direction;
	private float rotation;
	
	private float xSpeed;
	private float ySpeed;
	
	private float angle; //in degrees
	private float radians;
	
	private float lastRadians;
	
	private boolean ground;
	
	private Point left;
	private Point right;
	
	private short currentAction;
	
	private static final float xAccel = 0.003f;
	private static final float xDecel = 0.005f;
	private static final float maxAccel = 0.8f;
	private static final float maxSpeed = 1.3f;
	private static final float gravity = 0.03f;
	private static final float jumpPower = -0.95f;
	private static final float jumpMultiplier = 2.5f;
	private static final float minimumJump = -0.2f;
	private static final float rollingModifier = 2.3f;
	private static final float slopeFactor = 0.009f;
	private static final float rollingSlopeFactorUp = 0.005f;
	private static final float rollingSlopeFactorDown = 0.02f;
	
	private static final int radius = 26;
	
	public static final short ACTION_IDLE = 0;
	public static final short ACTION_CROUCHING = 1;
	public static final short ACTION_LOOKING_UP = 2;
	public static final short ACTION_RUNNING = 3;
	public static final short ACTION_ROLLING = 4;
	public static final short ACTION_SPINDASH = 5;
	public static final short ACTION_IN_AIR = 6;
	public static final short ACTION_JUMPING = 7;
	public static final short ACTION_AIR_ROLL = 8;
	
	interface TikalAction {
        void move();
    }
    private TikalAction[] tikalActions = new TikalAction[] {
        new TikalAction() { public void move() { handleIdleAction(); } },
        new TikalAction() { public void move() { handleCrouchingAction(); } },
        new TikalAction() { public void move() { handleLookUpAction(); } },
        new TikalAction() { public void move() { handleRunningAction(); } },
        new TikalAction() { public void move() { handleRollingAction(); } },
        new TikalAction() { public void move() { handleSpindashAction(); } },
        new TikalAction() { public void move() { handleInAirAction(); } },
        new TikalAction() { public void move() { handleJumpingAction(); } },
        new TikalAction() { public void move() { handleInAirAction(); } },
    };

	public TikalPlayer(Point spawn) {
		location = new Point(spawn.getX(), spawn.getY());
		direction = 1;
		rotation = 0f;
		
		xSpeed = 0.0f;
		ySpeed = 0.0f;
		
		angle = 0.0f;
		radians = 0.0f;
		lastRadians = angle;
		
		ground = false;
		
		left = new Point(0.0f, 0.0f);
		right = new Point (0.0f, 0.0f);
		
		currentAction = ACTION_IN_AIR;
	}
	
	@Override
	public Point getPoint() { return location; }

	public boolean isCollidingSide() {
		//System.out.println( "Side: " +  layer.getTileAt(
		//						new Point(	location.getX() + radius * direction * (float)Math.cos(radians), 
		//									location.getY() - radius * direction * (float)Math.sin(radians))));
		
		if (layer.getCollision(new Point(	location.getX() - radius * (float)Math.sin(radians) + radius * direction * (float)Math.cos(radians), 
											location.getY() - radius * (float)Math.cos(radians) - radius * direction * (float)Math.sin(radians))))
			return true;
		return false;
	}
	
	public boolean isCollidingSideWide() {
		//System.out.println( "Side: " +  layer.getTileAt(
		//						new Point(	location.getX() + radius * direction * (float)Math.cos(radians), 
		//									location.getY() - radius * direction * (float)Math.sin(radians))));
		
		if (layer.getCollision(new Point(	location.getX() - (radius * 2 - 1) * (float)Math.sin(radians) + (radius) * direction * (float)Math.cos(radians), 
											location.getY() - (radius * 2 - 1) * (float)Math.cos(radians) - (radius) * direction * (float)Math.sin(radians))) ||
			layer.getCollision(new Point(	location.getX() - 1 * (float)Math.sin(radians) + (radius) * direction * (float)Math.cos(radians), 
											location.getY() - 1 * (float)Math.cos(radians) - (radius) * direction * (float)Math.sin(radians))))
			return true;
		return false;
	}
	
	public boolean isCollidingTop() {
		if (layer.getCollision(new Point(	location.getX() - radius * 2 * (float)Math.sin(radians), 
											location.getY() - radius * 2 * (float)Math.cos(radians))))
			return true;
		return false;
	}
	public boolean isCollidingTopWide(float yOffset) {
		if (layer.getCollision(new Point(	location.getX() - (radius * 2 + yOffset) * (float)Math.sin(radians) - (radius - 1) * (float)Math.cos(radians), 
											location.getY() - (radius * 2 + yOffset) * (float)Math.cos(radians) - (radius - 1) * (float)Math.sin(radians))) ||
			layer.getCollision(new Point(	location.getX() - (radius * 2 + yOffset) * (float)Math.sin(radians) + (radius - 1) * (float)Math.cos(radians), 
											location.getY() - (radius * 2 + yOffset) * (float)Math.cos(radians) - (radius - 1) * (float)Math.sin(radians))))
			return true;
		return false;
	}
	public boolean isCollidingBottom() {
		if (layer.getCollision(new Point(	location.getX(), 
											location.getY())))
			return true;
		return false;
	}
	public int isCollidingBottomWide() {
		int result = 0;
		if (layer.getCollision(new Point(	location.getX() - (radius - 1) * (float)Math.cos(radians), 
											location.getY() - (radius - 1) * (float)Math.sin(radians))))
			result++;
		if (layer.getCollision(new Point(	location.getX() + (radius - 1) * (float)Math.cos(radians), 
											location.getY() - (radius - 1) * (float)Math.sin(radians))))
			result++;
		return result;
	}
	public boolean isCollidingGround(float xOffset, float yOffset) {
		if (layer.getCollision(new Point(	location.getX() + (1 + yOffset) * (float)Math.sin(radians) + (xOffset) * (float)Math.cos(radians), 
											location.getY() + (1 + yOffset) * (float)Math.cos(radians) - (xOffset) * (float)Math.sin(radians))))
			return true;
		return false;
	}
	public int isCollidingGroundWide(float xOffset, float yOffset) {
		int result = 0;
		if (isCollidingGround(-(radius + xOffset - 1f), yOffset))
			result++;
		if (isCollidingGround((radius + xOffset - 1f), yOffset))
			result++;
		return result;
	}
	
	@Override
	public boolean detectPlayerCollision(Player e) {
		// TODO Auto-generated method stub
		return false;
	}

	public void setLayer(Layer l) { layer = l; }
	
	public void init() {
		ResourceManager resourceManager = ResourceManager.getInstance();
		resourceManager.loadImages("src/assets/xml/testImages.xml");
		resourceManager.loadAnimation("src/assets/xml/tikalAnimation.xml");

		anim = resourceManager.getAnimation("tikalFall");
	}
	
	@Override
	public void update(int delta) {
		
		
		
		/*
		 * Physics
		 */
		if (!ground) {
			if (isCollidingTopWide(1))
				if (ySpeed < 0.0f) ySpeed = 0.0f; 
			
		}
		location.addY((float)(ySpeed * Math.cos(radians) - xSpeed * Math.sin(radians)) * delta);
		location.addX((float)(xSpeed * Math.cos(radians) + ySpeed * Math.sin(radians)) * delta);
		
		
		
		location = new Point(
						Math.min(Math.max(location.getX(), 0.0f), (float)layer.getRight()), 
						Math.min(Math.max(location.getY(), 0.0f), (float)layer.getBottom()));
		

		
		tikalActions[currentAction].move();
		
		if(xSpeed > xDecel)
			direction = 1;
		else if(xSpeed < -xDecel)
			direction = -1;
		
		handleXMovement(1.6f);
		
		if (!ground) {
			ySpeed += gravity;
			
			if(isCollidingTopWide(0)) {	
				while (isCollidingTop()) {
					location.addY((float) Math.cos(radians));
					location.addX((float) Math.sin(radians));
				}
			}
			if(isCollidingSideWide() && xSpeed * direction > 0.0f && !isCollidingGround(0,0)) {
				xSpeed = 0.0f;
				
				location.addX((xSpeed % 1) * -direction * (float) Math.cos(radians));
				location.addY((xSpeed % 1) * direction * (float) Math.sin(radians));
				while (isCollidingSideWide()) {
					
					location.addX(-direction * (float) Math.cos(radians));
					location.addY(direction * (float) Math.sin(radians));
				}
			}
			if (isCollidingGroundWide(0f, 0f) != 0 && ySpeed >= 0.0f) {
				
				System.out.println(isCollidingGroundWide(0f, 0f) + " " + isCollidingGroundWide(-4f, 56f) + " " + isCollidingGroundWide(-4f, 0f));				
				if (isCollidingGroundWide(0f,radius*2) == 2 || 
						isCollidingGroundWide(0f, 0f) == 1 &&
						isCollidingGroundWide(-4f, 56) == 1 &&
						isCollidingGroundWide(-4f, 0f) == 0) {
					findAngle();
					ground = true;
					xSpeed += ySpeed * -(float) Math.sin(radians) * 0.7;
					ySpeed = 0.0f;
					if (xSpeed == 0.0f)
						switchAction(ACTION_IDLE);
					else
						switchAction(ACTION_RUNNING);
				}
				else {
				
					ground = true;
					xSpeed += ySpeed * -(float) Math.sin(radians) * 0.7;
					ySpeed = 0.0f;
					if (xSpeed == 0.0f)
						switchAction(ACTION_IDLE);
					else
						switchAction(ACTION_RUNNING);
				}
				System.out.println("Angle is now: " + angle);
			}
		}
		
		if(ground) {
			for (int i = 0; i < 32; i++) {
				if(isCollidingBottom()) {
					location.addX(-(float) Math.sin(radians));
					location.addY(-(float) Math.cos(radians));
				}
				else break;
			}
			if (isCollidingGround(0, 32) && !isCollidingBottom())
				for (int i = 0; i < 32; i++) {
					if(!isCollidingGround(0, 0.5f)) {
						location.addX((float) Math.sin(radians));
						location.addY((float) Math.cos(radians));
					}
					else break;
				}
			
			if (isCollidingGroundWide(0, radius) == 2) findAngle();
			
			if(isCollidingSide() && xSpeed * direction > 0.0f ) {
				xSpeed = 0.0f;
				
				location.addX((xSpeed % 1) * -direction * (float) Math.cos(radians));
				location.addY((xSpeed % 1) * direction * (float) Math.sin(radians));
				while (isCollidingSide()) {
					
					location.addX(-direction * (float) Math.cos(radians));
					location.addY(direction * (float) Math.sin(radians));
				}
			}
			System.out.println();
			
			
		}

		
		/*
		 * Animation Selection
		 */
		handleAnimation(delta);
		
	}
	
	//location.getX() - radius * 2 * (float)Math.sin(radians), 
	//location.getY() - radius * 2 * (float)Math.cos(radians))))



	@Override
	public int getPlayerID() {
		return 0;
	}

	@Override
	public int getState() {
		// TODO Auto-generated method stub
		return currentAction;
	}

	public void switchAction(short action) {
		currentAction = action;
		anim.beginExit();
	}
	
	/*
	 * --------------------------------------------------------------
	 * 						Action Handles
	 * --------------------------------------------------------------
	 */
	
	private void handleIdleAction() {
		InputManager input = InputManager.getInstance();
		
		if (input.isKeyHeld(InputManager.DOWN)) {
			switchAction(ACTION_CROUCHING);
			return;
		}
		
		if (input.isKeyHeld(InputManager.UP)) {
			switchAction(ACTION_LOOKING_UP);
			return;
		}
		
		if(input.isKeyHeld(input.LEFT) || input.isKeyHeld(input.RIGHT)) {
			switchAction(ACTION_RUNNING);
			handleRunningAction();
			return;
		}
		
		if(input.isKeyPressed(InputManager.BUTTON1)) {
			startJumping();
			return;
		}
	}
	private void handleRunningAction() {
		InputManager input = InputManager.getInstance();
		
		if (input.isKeyPressed(InputManager.BUTTON1)) {
			startJumping();
			return;
		}
		
		
		
		if (	isCollidingGroundWide(0f, 24f) == 0 && !isCollidingGround(0, 0) && ground ||
				//!isCollidingGround() && lastRadians != 0.0f ||
				(angle > 70.0f && angle < 290.0f) && Math.abs(xSpeed) < xDecel) {
			System.out.println("wat");
			ySpeed = -xSpeed * (float) Math.sin(lastRadians);
			xSpeed = xSpeed * (float) Math.cos(lastRadians);
			
			handleToAirMovement();
			
			ground = false;
			lastRadians = radians;
			angle = 0.0f;
			radians = 0.0f;
			switchAction(ACTION_IN_AIR);
			return;
		}
		
		if(xSpeed == 0.0f) {
			switchAction(ACTION_IDLE);
			return;
		}
		
		if (input.isKeyHeld(InputManager.DOWN))
			switchAction(ACTION_ROLLING);
	}
	private void handleCrouchingAction() {
		InputManager input = InputManager.getInstance();
		
		if (!input.isKeyHeld(InputManager.DOWN)) {
			switchAction(ACTION_IDLE);
			return;
		}
		
		if (input.isKeyPressed(InputManager.BUTTON1)) {
		
			return;
		}
	}
	private void handleLookUpAction() {
		InputManager input = InputManager.getInstance();
		
		if (!input.isKeyHeld(InputManager.UP)) {
			switchAction(ACTION_IDLE);
			return;
		}
		
		if (input.isKeyPressed(InputManager.BUTTON1)) {
		
			return;
		}
	}
	private void handleJumpingAction() {
		InputManager input = InputManager.getInstance();
		
		 
		
		if (!input.isKeyHeld(input.BUTTON1) && ySpeed < minimumJump) {
			ySpeed = minimumJump;
		}
		
		
			
	}
	private void handleInAirAction() {
		//InputManager input = InputManager.getInstance();
		
		
			
	}
	private void handleRollingAction() {
		InputManager input = InputManager.getInstance();
		
		if (xSpeed < -xDecel) {
			xSpeed += xDecel;// * rollingModifier;
		}
		if (xSpeed > xDecel) {
			xSpeed -=xDecel;// * rollingModifier;
		}
		
		if ( ((xSpeed > 0.0) && (angle < 180)) || ((xSpeed < 0.0) && (angle > 180)))
			xSpeed -= (float) Math.sin(radians) * rollingSlopeFactorUp;
	    if ( ((xSpeed < 0.0) && (angle < 180)) || ((xSpeed > 0.0) && (angle > 180)))
	    	xSpeed -= (float) Math.sin(radians) * rollingSlopeFactorDown;
		
		if (Math.abs(xSpeed) < xDecel) {
			xSpeed = 0.0f;
			if(angle > 70 && angle < 290) {
				ground = false;
				lastRadians = radians;
				angle = 0.0f;
				radians = 0.0f;
				switchAction(ACTION_AIR_ROLL);
			}
			else
				switchAction(ACTION_IDLE);
			return;
		}
		
		if (input.isKeyPressed(InputManager.BUTTON1)) {
			startJumping();
			return;
		}
		
		if (isCollidingGroundWide(0f, 0f) == 0 && ground) {
			
			
			ySpeed = -xSpeed * (float) Math.sin(lastRadians);
			xSpeed = xSpeed * (float) Math.cos(lastRadians);
			
//		    Gravity               = global.SinTable[ReferenceAngle]*-Speed;
//		    Speed                 = global.CosTable[ReferenceAngle]*Speed;
			
			ground = false;
			switchAction(ACTION_AIR_ROLL);
			return;
		}
		
	}
	
	private void startJumping() {
		handleToAirMovement();
		ySpeed = jumpPower * (float) Math.cos(radians) - xSpeed * (float) Math.sin(radians);
		xSpeed = jumpPower * (float) Math.sin(radians) + xSpeed * (float) Math.cos(radians);
		
//	    Gravity               = global.CosTable[ReferenceAngle]*JumpStrength - global.SinTable[ReferenceAngle]*Speed;
//	    Speed                 = global.SinTable[ReferenceAngle]*JumpStrength + global.CosTable[ReferenceAngle]*Speed
		
		ground = false;
		
		lastRadians = radians;
		angle = 0.0f;
		radians = 0.0f;
		switchAction(ACTION_JUMPING);
	}
	
	private void findAngle() {
		lastRadians = radians;
		float useRadians = (float) Math.PI * (float) Math.floor(angle/45) * 45 / 180 ;
		left = new Point(	location.getX() - radius * (float) Math.sin(useRadians) - (float) Math.cos(useRadians)*14f, 
							location.getY() - radius * (float) Math.cos(useRadians) + (float) Math.sin(useRadians)*14f);
        right = new Point(	location.getX() - radius * (float) Math.sin(useRadians) + (float) Math.cos(useRadians)*14f,
        					location.getY() - radius * (float) Math.cos(useRadians) - (float) Math.sin(useRadians)*14f);
		
        for (int x = 0; x < 60; x++) {
            if (!layer.getCollision(left)) {
                left.addX(2f*(float) Math.sin(useRadians));
                left.addY(2f*(float) Math.cos(useRadians));
            }
            if (!layer.getCollision(right)) {
                right.addX(2f*(float) Math.sin(useRadians));
                right.addY(2f*(float) Math.cos(useRadians));
            }
        }
        
        angle = Angle.getAngle(	new Point(	1.0f, 0.0f),
        						new Point(	right.getX() - left.getX(),
        									right.getY() - left.getY()));
        if(right.getY() - left.getY() > 0)
        	angle = 360.0f - angle;
        System.out.println(angle);
        radians = (angle * ((float) Math.PI)) / 180f;
	}
	
	private void handleXMovement(float scale) {
		InputManager input = InputManager.getInstance();
		if (input.isKeyHeld(input.LEFT)) {
			if (-xSpeed < maxAccel)
				xSpeed -= xAccel * scale;
		}
		else if (xSpeed < -xDecel) {
			xSpeed += xDecel * scale;
		}
		
		if (input.isKeyHeld(input.RIGHT)) {
			if (xSpeed < maxAccel)
				xSpeed += xAccel * scale;
		}
		else if (xSpeed > xDecel) {
			xSpeed -=xDecel * scale;
		}
		
		if (ground == true && angle > 35 && angle <325)
	        xSpeed -= (float) Math.sin(radians) * slopeFactor * scale;
		
		if (	Math.abs(xSpeed) < xDecel 
				&& !input.isKeyHeld(input.LEFT)
				&& !input.isKeyHeld(input.RIGHT) ) {
			xSpeed = 0.0f;
		}
	}
	
	private void handleToAirMovement() {
		location.addY((float) radius - radius * (float) Math.cos(radians));
		location.addX((float) - radius * (float) Math.sin(radians));
	}
	
	private void handleSpindashAction() {
		
	}
	
	/*
	 * --------------------------------------------------------------
	 * 						Animation Handles
	 * --------------------------------------------------------------
	 */
	
	@Override
	public void draw(Camera camera) {
		// TODO Auto-generated method stub
		anim.draw(	location.getX() - radius * (float)Math.sin(radians) - camera.getCenter().getX(), 
					location.getY() - radius * (float)Math.cos(radians) - camera.getCenter().getY(),
					direction * -1, 1f,
					1f,
					360f - angle);
		
		anim.draw(	left.getX() - camera.getCenter().getX(), 
					left.getY() - camera.getCenter().getY(),
				direction * -0.1f, 0.1f,
				1f,
				360f - angle);
		anim.draw(	right.getX() - camera.getCenter().getX(), 
					right.getY() - camera.getCenter().getY(),
				direction * -0.1f, 0.1f,
				1f,
				360f - angle);
		
	}
	
	public void handleAnimation(int delta) {
		if (currentAction == ACTION_RUNNING) {
			
			anim.setSpeed( Math.abs(xSpeed) * 1.43f + 0.53f );
			if (	Math.abs(xSpeed) > 0.3 && !anim.getName().equals("tikalJog")
					|| Math.abs(xSpeed) <= 0.3 && !anim.getName().equals("tikalWalk"))
				anim.beginExit();
		}
		else
			anim.setSpeed(1f);
		
		anim.update(delta);
		
		
		if (anim.isEnded()) {
			anim.reset();
			ResourceManager resourceManager = ResourceManager.getInstance();
			switch(currentAction) {
			case ACTION_IDLE:
				anim = resourceManager.getAnimation("tikalIdle");
				break;
			case ACTION_CROUCHING:
				anim = resourceManager.getAnimation("tikalCrouch");
				break;
			case ACTION_LOOKING_UP: 
				anim = resourceManager.getAnimation("tikalLookUp");
				break;
			case ACTION_RUNNING:
				if (Math.abs(xSpeed) > 0.3f)
					anim = resourceManager.getAnimation("tikalJog");
				else 
					anim = resourceManager.getAnimation("tikalWalk");
				break;
			case ACTION_ROLLING:
				anim = resourceManager.getAnimation("tikalRoll");
				break;
			case ACTION_SPINDASH:
				anim = resourceManager.getAnimation("tikalSpindash");
				break;
			case ACTION_IN_AIR:
				if (ySpeed >= 0.0f)
					anim = resourceManager.getAnimation("tikalFall");
				else 
					anim = resourceManager.getAnimation("tikalRise");
				break;
			case ACTION_JUMPING:
				anim = resourceManager.getAnimation("tikalRoll");
				break;
			case ACTION_AIR_ROLL:
				anim = resourceManager.getAnimation("tikalRoll");
				break;
			}
		}
	}
}
